﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace Fadd.Logging
{
	/// <summary>
	/// Logger exposed to programmer.
	/// </summary>
	public class Logger : ILogger
	{
		private readonly ICollection<ITarget> _targets;
		private LoggerConfiguration _config;

		/// <summary>
		/// Initializes a new instance of the <see cref="Logger"/> class.
		/// </summary>
		/// <param name="targets">Logger targets.</param>
		/// <exception cref="ArgumentNullException"><c>targets</c> is null.</exception>
		public Logger(ICollection<ITarget> targets)
		{
			if (targets == null)
				throw new ArgumentNullException("targets");
			_targets = targets;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Logger"/> class.
		/// </summary>
		public Logger()
		{
			_targets = new List<ITarget>();
		}

		#region ILogger Members

		/// <summary>
		/// Used to be able to trace program execution.
		/// </summary>
		/// <remarks>
		/// Very detailed log messages, potentially of a high frequency and volume
		/// </remarks>
		/// <param name="message">Log message.</param>
		public void Trace(string message)
		{
			WriteEntry(LogLevel.Trace, message);
		}

		/// <summary>
		/// Used to be able to trace program execution.
		/// </summary>
		/// <remarks>
		/// Very detailed log messages, potentially of a high frequency and volume
		/// </remarks>
		/// <param name="message">Log message.</param>
		/// <param name="exception">Exception thrown in application.</param>
		public void Trace(string message, Exception exception)
		{
			WriteEntry(LogLevel.Trace, message, exception);
		}

		/// <summary>
		/// Entries that are helpful when debugging.
		/// </summary>
		/// <param name="message">Log message.</param>
		public void Debug(string message)
		{
			WriteEntry(LogLevel.Debug, message);
		}

		/// <summary>
		/// Entries that are helpful when debugging.
		/// </summary>
		/// <param name="message">Log message.</param>
		/// <param name="exception">Exception thrown in application.</param>
		public void Debug(string message, Exception exception)
		{
			WriteEntry(LogLevel.Debug, message, exception);
		}

		/// <summary>
		/// Information that can be helpful during normal program execution.
		/// </summary>
		/// <param name="message">Log message.</param>
		public void Info(string message)
		{
			WriteEntry(LogLevel.Info, message);
		}

		/// <summary>
		/// Information that can be helpful during normal program execution.
		/// </summary>
		/// <param name="message">Log message.</param>
		/// <param name="exception">Exception thrown in application.</param>
		public void Info(string message, Exception exception)
		{
			WriteEntry(LogLevel.Info, message, exception);
		}

		/// <summary>
		/// Something unexpected happened, but program execution can continue as expected.
		/// </summary>
		/// <param name="message">Log message.</param>
		public void Warning(string message)
		{
			WriteEntry(LogLevel.Warning, message);
		}

		/// <summary>
		/// Something unexpected happened, but program execution can continue as expected.
		/// </summary>
		/// <param name="message">Log message.</param>
		/// <param name="exception">Exception thrown in application.</param>
		public void Warning(string message, Exception exception)
		{
			WriteEntry(LogLevel.Warning, message, exception);
		}

		/// <summary>
		/// Something went really wrong. Program execution cannot continue as expected,
		/// but there is no need to shutdown the program.
		/// </summary>
		/// <param name="message">Log message.</param>
		public void Error(string message)
		{
			WriteEntry(LogLevel.Error, message);
		}

		/// <summary>
		/// Something went really wrong. Program execution cannot continue as expected,
		/// but there is no need to shutdown the program.
		/// </summary>
		/// <param name="message">Log message.</param>
		/// <param name="exception">Exception thrown in application.</param>
		public void Error(string message, Exception exception)
		{
			WriteEntry(LogLevel.Error, message, exception);
		}

		/// <summary>
		/// Major disaster, program ought to be shut down and restarted.
		/// </summary>
		/// <param name="message">Log message.</param>
		public void Fatal(string message)
		{
			WriteEntry(LogLevel.Fatal, message);
		}

		/// <summary>
		/// Major disaster, program ought to be shut down and restarted.
		/// </summary>
		/// <param name="message">Log message.</param>
		/// <param name="exception">Exception thrown in application.</param>
		public void Fatal(string message, Exception exception)
		{
			WriteEntry(LogLevel.Fatal, message, exception);
		}

		/// <summary>
		/// Load configuration
		/// </summary>
		/// <param name="configuration">Configuration to load</param>
		/// <remarks>
		/// Can only be invoked during startup.
		/// </remarks>
		/// <exception cref="InvalidOperationException">Configuration have already been specified.</exception>
		public void LoadConfiguration(LoggerConfiguration configuration)
		{
			if (_config != null)
				throw new InvalidOperationException("Configuration have already been specified.");

			_config = configuration;
			Name = _config.Name;
			NameSpace = _config.Namespace;

			if (_targets.Count != 0) 
				return;

			foreach (var target in _config.Targets)
				_targets.Add(target);
		}

		/// <summary>
		/// Gets minimum log level that can be logged.
		/// </summary>
		public LogLevel MinLevel
		{
			get { return _config.MinLevel; }
		}

		/// <summary>
		/// Gets or sets logger name.
		/// </summary>
		public string Name { get; set; }

		/// <summary>
		/// Gets or sets name space to log.
		/// </summary>
		public string NameSpace { get; set; }

		/// <summary>
		/// Determines if this logger can log the specified name space.
		/// </summary>
		/// <param name="value">Name space to check</param>
		/// <returns>true if name space can be logged; otherwise false.</returns>
		public bool CanLog(string value)
		{
			if (_config.IsWildCard)
			{
				// only a wild card.
				if (_config.Namespace == string.Empty)
					return true;

				if (value.Length < _config.Namespace.Length)
					return false;

				string ns = value == _config.Namespace
				            	? value
				            	: value.Substring(0, _config.Namespace.Length);
				if (ns == _config.Namespace)
					return true;
			}

			return value == _config.Namespace;
		}

		/// <summary>
		/// Targets that this logger writes to.
		/// </summary>
		public IEnumerable<ITarget> Targets
		{
			get { return _targets; }
		}

		#endregion

		internal void AddTarget(ITarget target)
		{
			_targets.Add(target);
		}

		internal bool ContainsTarget(ITarget target)
		{
			return _targets.Contains(target);
		}


		/// <summary>
		/// Writes the entry.
		/// </summary>
		/// <param name="level">The level.</param>
		/// <param name="message">The message.</param>
		protected virtual void WriteEntry(LogLevel level, string message)
		{
			if (level < _config.MinLevel)
				return;

			LogEntry entry = CreateEntry(level, message);
			WriteToTargets(entry, _targets);
		}

		/// <summary>
		/// Writes the entry.
		/// </summary>
		/// <param name="level">The level.</param>
		/// <param name="message">The message.</param>
		/// <param name="exception">The exception.</param>
		protected virtual void WriteEntry(LogLevel level, string message, Exception exception)
		{
			if (level < _config.MinLevel)
				return;

			LogEntry entry = CreateEntry(level, message, exception);
			WriteToTargets(entry, _targets);
		}

		/// <summary>
		/// Write entry to all targets
		/// </summary>
		/// <param name="entry">log entry</param>
		/// <param name="targets">targets</param>
		protected virtual void WriteToTargets(LogEntry entry, IEnumerable<ITarget> targets)
		{
			foreach (ITarget target in _targets)
				target.Write(entry);
		}

		/// <summary>
		/// Create a new log entry
		/// </summary>
		/// <param name="level">Log level</param>
		/// <param name="msg">Message entered by user</param>
		/// <returns>A new log entry</returns>
		protected virtual LogEntry CreateEntry(LogLevel level, string msg)
		{
			return new LogEntry
			       	{
			       		CreatedAt = DateTime.Now,
			       		Level = level,
			       		Message = msg,
                        StackFrames = new StackTrace(SkipCount).GetFrames(),
			       		ThreadId = Thread.CurrentThread.ManagedThreadId,
						Logger = this
			       	};
		}

		/// <summary>
		/// Create a new log entry object.
		/// </summary>
		/// <param name="level">Log level</param>
		/// <param name="msg">Message written by user</param>
		/// <param name="exception">Exception thrown in application</param>
		/// <returns>A new log entry</returns>
		protected virtual LogEntry CreateEntry(LogLevel level, string msg, Exception exception)
		{
			return new LogEntry
			       	{
			       		CreatedAt = DateTime.Now,
			       		Exception = exception,
			       		Level = level,
			       		Message = msg,
                        StackFrames = new StackTrace(SkipCount).GetFrames(),
			       		ThreadId = Thread.CurrentThread.ManagedThreadId,
						Logger = this
			       	};
		}

        /// <summary>
        /// Gets number of frames to skip.
        /// </summary>
	    protected virtual int SkipCount
	    {
            get { return 2; }
	    }
	}
}